package client;

import info.clearthought.layout.TableLayout;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.border.BevelBorder;
import javax.swing.border.EtchedBorder;

import nanoxml.XMLElement;
import utils.ITableConnection;
import utils.PlayerListGUI;
import utils.Settings;
import utils.Table;
import utils.TableList;
import utils.Utils;

import commands.CommandArmy;
import commands.CommandChatMessage;
import commands.CommandGameResultToOwner;
import commands.CommandLeaveTable;
import commands.CommandMove;
import commands.CommandReadyToStart;
import components.ChatPanel;
import components.Countdown;
import components.LogoPanel;
import components.RPSButton;
import components.RPSGlassPane;
import components.RPSList;
import components.RPSPanel;

/**
 * The frame where the game is played
 */
public class TableFrame extends JFrame {
    
	private static final long serialVersionUID = 1L;
	
	private static final int SPACING = 15;
	
	private static final int COUNTDOWN_FOR_TURN = 0;
	private static final int COUNTDOWN_FOR_PLAYER_TO_RETURN = 1;
	
	private static final Font FONT_WHOSE_TURN = new Font("Courier New", Font.BOLD, 32);
	private static final Font FONT_PLAYER_NAME = new Font("Courier New", Font.BOLD, 16);

	private String			  username;			// Username from conn.getUsername();
	private Table			  table = null;
	private ITableConnection  tableConn = null;
	
	protected Model 	  model = null;			// Client gameModel
	protected Controller  controller = null;	// GameController
	protected Board 	  board = null;			// main view
	
	//GUI
	private RPSGlassPane		glassPane = null;    
	private RPSButton			buttonStart = null;
	private RPSButton			buttonExit = null;
	private RPSButton			buttonGameSettings = null;
	private ChatPanel			chatPanel = null;
	private RPSPanel			gamePanel = null;
	private RPSPanel			buttonsPanel = null;
	private RPSButton 			shuffleButton = null;
	private RPSButton 			doneShuffleButton = null;
	private boolean 			wasShuffeled = false;
	private MovePieceAnimation	movePieceAnimation;
	public JLabel				labelWhoseTurn = null;
	public RPSList				jlistHistory = null;
	private JLabel				labelRedPlayer = null;
	private JLabel				labelBluePlayer = null;
	
	private Countdown			countdown = null;
	private GameSettings		gameSettings = null;
	private TableList			tableList = null;
	
    private int countdownReason;


	public TableFrame (ITableConnection tableConn, TableList tableList) {
		super();
		
		this.tableConn = tableConn;
		this.table = tableConn.getTable();
		this.tableList = tableList;
		this.username = tableConn.getUsername();
		
		countdownReason = COUNTDOWN_FOR_TURN;
		
	    movePieceAnimation = new MovePieceAnimation();

		String title = Settings.TABLE_FRAME_TITLE_PREFIX + " - " + tableConn.getUsername();
		setTitle (title);
		
		gameSettings = tableConn.getTable().getGameSettings();
		
		setMVC(gameSettings, tableConn);
		
		setUpGUI ();

		updateHistory();
		
		// If I'm the owner of this table, automatically simulate the pressing of the Start button
		startIfOwner();

		// Make the window not close automatically, to give the user a chance to regret
		setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
	}


	private void setMVC(GameSettings gameSettings, ITableConnection tableConn)
	{
		model = new Model (gameSettings, tableConn.getTable().getPlayerList().getPlayer(username));
		setModelSettings();
		model.reset ();
		
		board = new Board (model);
		controller = new Controller (model, board);
		
		model.addObserver (board);
		controller.setConnection (tableConn);
		board.setControllerAsListener (controller);
	}
	
	private void setModelSettings() {
		model.setPlayerColor(table.getPlayerList().getPlayer(username).getColor());
		model.setOwnerColor(table.getPlayerList().getPlayer(table.getOwnerUsername()).getColor());
	}

	private void setUpGUI () {
		
	    double pref = TableLayout.PREFERRED;
	    double fill = TableLayout.FILL;
	    double [][] sizes;
    
	    //Create glass pane
		this.glassPane = new RPSGlassPane ();
		getRootPane().setGlassPane (glassPane);
		
	    // Create MainGamePanel - Logo , Buttons , Game , Chat
	    sizes = new double [][] {{pref}, {pref, pref, fill, pref}};
	    RPSPanel mainPanel = new RPSPanel(new TableLayout(sizes));
	    mainPanel.setBorder(BorderFactory.createEtchedBorder());
	    
	    sizes = new double [][] {{fill}, {pref}};
		RPSPanel topPanel = new RPSPanel (new TableLayout(sizes));
	    
	    //Create Buttons for TopPanel
	    sizes = new double [][] {{SPACING, pref, SPACING, pref, fill}, {pref}};
	    RPSPanel buttonsTopPanel = new RPSPanel(new TableLayout(sizes));
	    buttonsTopPanel.setBorder(BorderFactory.createEtchedBorder());
	    buttonStart = new RPSButton("Start");
	    buttonExit = new RPSButton("Exit");
	    buttonGameSettings = new RPSButton("Game Settings");
	    
	    buttonsTopPanel.add(buttonStart, "1, 0");   
	    buttonsTopPanel.add(buttonExit, "3, 0");   
	    buttonsTopPanel.add(buttonGameSettings, "4, 0, r, c");
	    
	    topPanel.add(buttonsTopPanel,"0, 0");
	    
	    
	    // Create MainGamePanel
	    sizes = new double [][] {{pref, fill}, {fill}};
	    gamePanel = new RPSPanel(new TableLayout(sizes));
	    
	    //Create GizmoPanel - RightSide
	    sizes = new double [][] {{fill}, {0.2, 0.4, 0.4}};
	    RPSPanel gizmoPanel = new RPSPanel(new TableLayout(sizes));
	    gizmoPanel.setBorder( BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
	    
	    sizes = new double [][] {{fill}, {fill}};
	    RPSPanel upperRightPanel = new RPSPanel(new TableLayout(sizes));
	    upperRightPanel.setBorder( BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
	    
	    labelWhoseTurn = new JLabel("RED TURN");
	    labelWhoseTurn.setFont(FONT_WHOSE_TURN);
	    labelWhoseTurn.setForeground(Color.RED);
	    labelWhoseTurn.setVisible(false);
	    upperRightPanel.add(labelWhoseTurn, "0, 0, c ,c");
	    
	    //Create Countdownpanel
	    sizes = new double [][] {{Countdown.TOTAL_WIDTH}, {Countdown.TOTAL_HEIGHT}};
	    RPSPanel countdownPanel = new RPSPanel(new TableLayout(sizes));
	    countdownPanel.setBorder( BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
	    if (Boolean.parseBoolean(gameSettings.getSetting(GameSettings.PROPERTY_KEY_DOES_LIMIT_TURN_TIME)))
	    {
		    countdown = new Countdown(Integer.parseInt(gameSettings.getSetting(GameSettings.PROPERTY_KEY_TURN_TIME_LIMIT_SECONDS)), this); //COUNTDOWN_TIME);
		    countdown.setVisible(false);
		    countdownPanel.add(countdown, "0, 0");  
	    }
	    
	    //Create shufflePanel
	    sizes = new double [][] {{fill}, {pref, fill, pref, pref}};
	    RPSPanel lowerRightPanel = new RPSPanel(new TableLayout(sizes));
	    lowerRightPanel.setBorder( BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
	    
	    //Create shuffleButtonsPanel
	    sizes = new double [][] {{pref, SPACING, pref}, {SPACING, pref, SPACING}};
	    buttonsPanel = new RPSPanel(new TableLayout(sizes));
	    shuffleButton = new RPSButton("Shuffle");
	    doneShuffleButton = new RPSButton("Done");
	    buttonsPanel.add(shuffleButton, "0, 1");   
	    buttonsPanel.add(doneShuffleButton, "2, 1");   
	    
	    shuffleButton.setVisible(false);
	    doneShuffleButton.setVisible(false);
	    doneShuffleButton.setEnabled(false);
	    
	    labelRedPlayer = new JLabel(" Red  : " + table.getOwnerUsername());
	    labelRedPlayer.setFont(FONT_PLAYER_NAME);
	    labelRedPlayer.setForeground(Color.RED);
	    
	    labelBluePlayer = new JLabel(" Blue : ");
	    String usernameOtherPlayer = table.getOtherPlayerUsername();
	    if (usernameOtherPlayer != null && usernameOtherPlayer.isEmpty() == false)
	    {
	    	labelBluePlayer.setText(labelBluePlayer.getText() + usernameOtherPlayer);
	    }
	    labelBluePlayer.setFont(FONT_PLAYER_NAME);
	    labelBluePlayer.setForeground(Color.BLUE);

	    lowerRightPanel.add(buttonsPanel, "0, 0, c, t");
	    lowerRightPanel.add(labelRedPlayer, "0, 2");
	    lowerRightPanel.add(labelBluePlayer, "0, 3");
	    
	    gizmoPanel.add(upperRightPanel, "0, 0");
	    gizmoPanel.add(countdownPanel, "0, 1");
	    gizmoPanel.add(lowerRightPanel, "0, 2");
	    
	    gamePanel.add(board, "0,0");
	    gamePanel.add(gizmoPanel, "1,0");
	    

	    //Create BottomPanel
	    sizes = new double [][] {{200, 200, fill}, {150}};
		RPSPanel bottomPanel = new RPSPanel (new TableLayout(sizes));

		// Create the history
		sizes = new double[][] { {fill}, {fill} };
		RPSPanel panelHistory = new RPSPanel(new TableLayout(sizes));
		panelHistory.setBorder("Moves History");
		jlistHistory = new RPSList();
		jlistHistory.setBorder(null);
		JScrollPane paneHistory = new JScrollPane(jlistHistory);
		paneHistory.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
		panelHistory.add(paneHistory, "0, 0");
		
		// Create UserListPanel 
		sizes = new double[][] { {fill}, {fill} };
		RPSPanel panelPlayerListGUI = new RPSPanel(new TableLayout(sizes));
		panelPlayerListGUI.setBorder("Current Connected Users");
		PlayerListGUI playerListGUI = new PlayerListGUI(tableConn.getTable().getPlayerList());
		panelPlayerListGUI.add(playerListGUI, "0, 0");
		
	    //Create ChatPanel
	    chatPanel = new ChatPanel(tableConn, 0);
	 
	    bottomPanel.add(panelHistory, "0, 0");  
	    bottomPanel.add(panelPlayerListGUI, "1, 0");  
	    bottomPanel.add(chatPanel , "2, 0");
	    
	    
	    // Create Logo Panel
	    LogoPanel logoPanel = new LogoPanel();
	    
	    mainPanel.add(logoPanel, "0, 0");   
	    mainPanel.add(topPanel, "0, 1");
	    mainPanel.add(gamePanel, "0, 2");
	    mainPanel.add(bottomPanel, "0, 3");
	    
	    // Add the main panel to the content pane
		getContentPane().add(mainPanel);
		
		pack();
	    
	    Dimension screenSize = getToolkit().getScreenSize();
		setLocation((int)screenSize.getWidth() / 2 - this.getWidth() / 2, (int)(screenSize.getHeight() - this.getHeight()) / 5);
		setVisible (true);
		setResizable(false);
		
		addListeners();
	}
	
	public void setShuffleButton (boolean bool) {
		shuffleButton.setVisible(bool);
		buttonsPanel.revalidate();
		repaint();
	}
	
	private void addListeners()
	{
		addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				leaveTable ();
			}
		});

		buttonStart.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e)
			{
				// If not the owner pressed Start (Which for him happens automatically), then set his name as the other player that
				// will be playing
				if (username.equals(table.getOwnerUsername()) == false)
				{
					// If the user is already playing in a table, notify him and don't allow him to Start this table
					if (Utils.isUserPlayingInATable(tableList, username))
					{
						String message = "You already playing at another table, so you cannot start playing at this one.";
						JOptionPane.showMessageDialog(TableFrame.this, message, "Starting A Table", JOptionPane.INFORMATION_MESSAGE);
						
						return;
					}

					table.setOtherPlayerUsername(username);
				}
				
				CommandReadyToStart commReadyToStart = new CommandReadyToStart (username);
				tableConn.send (commReadyToStart);
				
				// Disable the Start button
				disableStartButton();
			}
		});
		
		buttonExit.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e)
			{
				leaveTable();
			}
		});
		
		buttonGameSettings.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e)
			{
				// Open the New Game Settings dialog
				NewGameSettingsDialog newGameSettingsDialog = new NewGameSettingsDialog(TableFrame.this, null);
				newGameSettingsDialog.showCurrentSettings(gameSettings);
			}
		});
		
		shuffleButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e)
			{
				model.shuffleArmy();
				if (!wasShuffeled) {
					doneShuffleButton.setEnabled(true);
					doneShuffleButton.setVisible(true);
					
					buttonsPanel.revalidate();
					repaint();
					
					wasShuffeled = true;
				}
			}
		});
		
		doneShuffleButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e)
			{
				buttonsPanel.setVisible(false);
				buttonsPanel.revalidate();
				repaint();
				
				model.setGameState(Model.GAME_STATE_FINISHED_BUILDING_ARMY);
				
				tableConn.send(new CommandArmy(model.getArmy(), username));
				
				if (model.gotEnemyArmy) {
					model.setGameState(Model.GAME_STATE_PLAYING);
					labelWhoseTurn.setVisible(true);
					startCountDown();
				}
			}
		});
	}
	
	private void leaveTable() {
		int optionChosen = -1;
		
		if (model.isInPlay()) {
			String message = "Are you sure you want to quit the game ?";
			message += (model.isPlayerViewer()) ? "" : Settings.NEW_LINE + "(This will be consdired a lost)";

			optionChosen = JOptionPane.showConfirmDialog(this, message, "Quit Game ?",
					 									 JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
		}
		else {
			optionChosen = JOptionPane.YES_OPTION;
		}
		
		// Check if the user hasn't canceled the InputDialog, meaning there is an answer
		if (optionChosen == JOptionPane.YES_OPTION)
		{
			CommandLeaveTable commandLeaveTable = new CommandLeaveTable (username, tableConn.getTable().getTableNum());
			tableConn.leaveTable(commandLeaveTable);
			
			// Now close the frame and remove any references to it
	        closeTableFrame ();
		}
	}

	public void closeTableFrame() {
		 setVisible (false);
	     dispose ();	
	}
	
	public void startGame() {
		this.controller.start();
	}
	public Model getModel() {
		return model;
	}
	public void setModel(XMLElement xmlModel) {
		this.model.setState(xmlModel);
	}
	public RPSGlassPane getGlass() {
		return glassPane;
	}
	public void receiveGameChat (CommandChatMessage chatMessage) {
	    String messageText = chatMessage.getChat();
	    String usernameFrom = chatMessage.getUsername();
	    chatPanel.receiveMessage (usernameFrom, messageText);
	}
	
	private void startIfOwner()
	{
		if (username.equals(table.getOwnerUsername()))
		{
			buttonStart.doClick();
		}
	}
	
	public void disableStartButton()
	{
		buttonStart.setEnabled(false);
	}

	public void startCountDown() {
		if (countdown != null)
		{
			countdown.startTimer();
			countdown.setVisible(true);
		}
	}

	public void restartCountDown() {
		if (countdown != null)
		{
			countdown.restart();
		}
	}
	
	public void freezeCountDown() {
		if (countdown != null)
		{
			countdown.freeze();
		}
	}

	public void setCountDownForPlayerReturn()
	{
		countdownReason = COUNTDOWN_FOR_PLAYER_TO_RETURN;
		
		if (countdown == null)
		{
			countdown = new Countdown(Settings.NUM_OF_SECS_WAIT_FOR_PLAYER, this);
		}

		countdown.setTimer(Settings.NUM_OF_SECS_WAIT_FOR_PLAYER, Color.BLUE);
    	countdown.setVisible(true);
		countdown.startTimer();
	}
	
	public void setCountDownForGame()
	{
		countdownReason = COUNTDOWN_FOR_TURN;
		
		if (Boolean.parseBoolean(gameSettings.getSetting(GameSettings.PROPERTY_KEY_DOES_LIMIT_TURN_TIME)))
	    {
			countdown.freeze();
			Color color = (table.getCurrentPlayer().toString().toLowerCase().equals("blue")) ? Color.BLUE : Color.RED;
			countdown.setTimer(Integer.parseInt(gameSettings.getSetting(GameSettings.PROPERTY_KEY_TURN_TIME_LIMIT_SECONDS)), color);
			
			boolean isTimerVisible = (model.getGameState() == Model.GAME_STATE_PLAYING);
	    	countdown.setVisible(isTimerVisible);
	    }
		else
		{
			if (countdown != null)
			{
				countdown.freeze();
				countdown.setVisible(false);
			}
		}
	}
	
	public void countdownFinished()
	{
		if (countdownReason == COUNTDOWN_FOR_TURN)
		{
			// If this is the current player, make an empty move so that the turn will be switched to the next player
			if (model.isPlayerViewer() == false && model.playerColor == table.getCurrentPlayer())
			{
				// Switch turn
				switchTurn();
				model.animationOn = false;
				restartCountDown();
				board.setPressedPointOffBoard();
				table.moveTurn();
				
				CommandMove commandMove = new CommandMove(new Move(table.getCurrentPlayer(), Board.createOutOfBoardPoint(), Board.createOutOfBoardPoint()));
				tableConn.send(commandMove);
			}
		}
		else if (countdownReason == COUNTDOWN_FOR_PLAYER_TO_RETURN)
		{
			// The disconnected player has lost the game
			endGame();
			countdown.setVisible(false);
			CommandGameResultToOwner commandGameResultToOwner = null;
			
			String message = "The user hasn't returned. This is a technical lost for him." + Settings.NEW_LINE;
			if (model.isPlayerViewer() == false)
			{
				message += "You won !";
				if (model.isOwner() == true) {
					commandGameResultToOwner = new CommandGameResultToOwner("Red");
				}
				else {
					commandGameResultToOwner = new CommandGameResultToOwner("Blue");
				}
				tableConn.send(commandGameResultToOwner);
			}
			else
			{
				message += "The othe player won.";
			}
			JOptionPane.showMessageDialog(this, message, "Game Over", JOptionPane.INFORMATION_MESSAGE);
		}
	}
	
	public void switchTurn() {
		if (labelWhoseTurn.getText().equals("RED TURN")) {
			labelWhoseTurn.setText("BLUE TURN");
			labelWhoseTurn.setForeground(Color.BLUE);
		}
		else {
			labelWhoseTurn.setText("RED TURN");
			labelWhoseTurn.setForeground(Color.RED);
		}
	}
	
	public MovePieceAnimation getMovePieceAnimation()
	{
		return movePieceAnimation;
	}

	public void endGame()
	{
		model.setGameState(Model.GAME_STATE_GAME_OVER);
		
		if (buttonsPanel.isVisible())
		{
			shuffleButton.setEnabled(false);
			doneShuffleButton.setEnabled(false);
		}
		
		// Freeze the countdown
		freezeCountDown();
	}

	public void showMessagePlayerReturned(String usernameJoined)
	{
		String message = "The user " + usernameJoined + " returned. The game continues";
		if (Boolean.parseBoolean(gameSettings.getSetting(GameSettings.PROPERTY_KEY_DOES_LIMIT_TURN_TIME)) && model.getGameState() == Model.GAME_STATE_PLAYING)
		{
			message += Settings.NEW_LINE + "(The first turn will not be time-limited)";
		}
						 
		JOptionPane.showMessageDialog(this, message, "Game Continues", JOptionPane.INFORMATION_MESSAGE);
	}


	public boolean isCountdownVisible()
	{
		return (countdown != null && countdown.isVisible());
	}
	
	public void addToHistory(Point from, Point to)
	{
		table.addMoveToHistory(new Move(table.getCurrentPlayer(), from, to));
		updateHistory();
	}
	
	public void updateHistory()
	{
		jlistHistory.setListData(table.getHistoryStrings().toArray());
	}


	public void setBluePlayerLabel(String blueUsername)
	{
		labelBluePlayer.setText(labelBluePlayer.getText() + blueUsername);
	}
}
